/**
 * Software License, Version 1.0
 *
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 *
 *
 *Redistribution and use in source and binary forms, with or without
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For
 * further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and
 * wherever such third-party acknowledgments normally appear.
 *
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering,
 * shall not be used to endorse or promote products derived from this software
 * without prior written permission from Indiana University.  For written
 * permission, please contact the Advanced Research and Technology Institute
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering,
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 *
 *
 * Indiana University provides no reassurances that the source code provided
 * does not infringe the patent or any other intellectual property rights of
 * any other entity.  Indiana University disclaims any liability to any
 * recipient for claims brought by any other entity based on infringement of
 * intellectual property rights or otherwise.
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.transport.udp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import cgl.narada.transport.Link;
import cgl.narada.transport.LinkFactory;
import cgl.narada.transport.TransportDebugFlags;
import cgl.narada.transport.TransportException;
import cgl.narada.transport.TransportHandler;
import cgl.narada.util.PortRetriever;

/** This is a UDP implementation of the LinkFactory Interface. Lets assume that
    the connection is being initiated by a node A to another node B. There are
    two independent factors that need to borne in mind while using LinkFactory.
    The first is of course setting the link factory properties. The second is
    specifying the properties to create a link to some other node B.

    <p>
    To initialize the UDPLinkFactory one needs to specify <b>UDPListenerPort
    </b>, this is the port on which the process running this factory would
    listen to for datagram packet communications.
    <p>

    To create a link to node B. The following properties needs to be specified.
    <li><b>hostname</b> -  The host on which node B's process is running
    <li><b>portnum</b> - The portnum on which node B is listening to, for
    datagram packets.

    @author Shrideep Pallickara
    $Date$
    $Revision$
*/

public class UDPLinkFactory implements LinkFactory, TransportDebugFlags {

  private String managedLinkType = "udp";
  private Properties properties;
  private String moduleName="UDPLinkFactory: ";
  private int udpListenerPort=4000;
  protected UDPReceiverThread udpReceiverThread;

  protected Hashtable links;
  protected TransportHandler transportHandler;
  protected DatagramSocket udpListenerSocket;


  protected UDPPingHandler udppinghandler;

  public UDPLinkFactory() {
    links = new Hashtable();
    udppinghandler=new UDPPingHandler(this);
    udppinghandler.setDebug(UDPLinkFactory_Debug);
    udppinghandler.start();

  }


  /* These are the set of properties that are needed to set up the
     link Factory. */
  public void setLinkFactoryProperties(Properties factoryProperties) {
    if (factoryProperties == null) {
      System.out.println(moduleName + "The specified properties is NULL");
      return;
    }
    properties = factoryProperties;
    String _valueString = properties.getProperty("UDPListenerPort");
    if (_valueString != null) {
      udpListenerPort = Integer.parseInt(_valueString);
    } else {
      udpListenerPort =
	PortRetriever.retrieveAvailableDatagramPort(udpListenerPort);
      System.out.println(moduleName + "Property UDPListenerPort missing, " +
			 " will use [" + udpListenerPort + "]");
    }
  }


  /** Return the type of all the links created/managed by this factory */
  public String getManagedLinksType() {
    return managedLinkType;
  }

  /** Create a communication link based on the properties that have been
      specified. */
  public Link createLink(Properties linkProperties)
    throws TransportException {
    if (linkProperties == null) {
      throw new TransportException(moduleName +
				   "The specified link propertis is null");
    }

    String _hostName = linkProperties.getProperty("hostname");
    String _portString = linkProperties.getProperty("portnum");

    if (_hostName == null || _portString == null) {
      throw new TransportException(moduleName +
				   "One of the specified properties is null");
    }
    int _portNum = Integer.parseInt(_portString);

    try {
      InetAddress _ia = InetAddress.getByName(_hostName);
      //_hostName = _ia.getHostName();
      _hostName = _ia.getHostAddress();
      String _key = "udp://" + _hostName + ":" + _portNum;
      if (links.containsKey(_key)) {
	throw new TransportException(moduleName + "Link already exists to" +
				     "udp://" + _key);
      }
      UDPLink _link =
	new UDPLink(udpListenerSocket, _ia, _portNum, this);
      _link.setLinkId(_key);

      links.put(_key, _link);
      transportHandler.registerLink(this, _link);

      /** Force the creation of a link on the other side too */
      byte[] udpConnRequest = new byte[1];
      udpConnRequest[0] = cgl.narada.protocol.Protocol.CONN_REQ;
      _link.sendData(udpConnRequest);
      
      if(linkProperties.getProperty("FailureDetection","false").equals("true")){
    	  udppinghandler.addLinkForMonitoring(_link);
      }
      
      return _link;
    } catch (UnknownHostException uhe) {
      String errorInfo = moduleName + "Unknown host [" + _hostName + "]" + uhe;
      System.out.println(errorInfo);
      throw new TransportException(errorInfo);
    }  catch (IOException ioe) {
      String errorInfo = moduleName + " Unable to Setup Connection to [" +
	_hostName + ":" + _portNum + "] => " + ioe;
      System.out.println(errorInfo);
      throw new TransportException(errorInfo);
    }
  }

  /** Set the interval for checking status of all links managed by this
      factory to the one specified in the argument */
  public void setLinkStatusInterval(long interval) {


  }

  /** Starts communication services. In this case what we do is start the
      UDPListener thread, which listens to datagram packets arriving at the
      UDPListenerPort */
  public boolean loadLinkFactoryServices() throws TransportException {
    try {
      udpListenerSocket = new DatagramSocket(udpListenerPort);
    } catch (SocketException se) {
      throw new TransportException (moduleName + se);
    }
    udpReceiverThread = new UDPReceiverThread(udpListenerSocket, this, udppinghandler);
    udpReceiverThread.start();
    System.out.println("Loaded UDPLinkFactory communication services");
    return true;
  }

  /** Indicates if this factory can manage migration of links of a certain
      type */
  public boolean canManageLinkMigrationOfType(String linkType) {
    /**NBNI*/
    return false;
  }

  /** Used to migrate communications of a link to the one supported by
      links managed by this factory */
  public void manageLinkMigration(Link link) throws TransportException{
    /**NBNI*/
  }


  /** Sets the reference to the transport handler. This is done during the
      loadLinkFactory() operation within the transport handler
      implementations. */
  public void setTransportHandler(TransportHandler transportHandler) {
    this.transportHandler = transportHandler;
  }

  /** This method is used to garbage collect any resources associated with
      the link factory */
  public void dispose() {
    Enumeration _links = links.elements();
    while(_links.hasMoreElements()) {
      UDPLink _udpLink = (UDPLink) _links.nextElement();
      _udpLink.closeLink();
    }
    udpReceiverThread.shutdownServices();
    udpListenerSocket.close();
    
  }

  public UDPLink getCurrentLink(InetAddress remoteAddress,int remotePort){

    String _linkId = "udp://" +
      remoteAddress.getHostAddress() + ":" + remotePort;

    UDPLink linkobject=null;
    if (links.containsKey(_linkId)) {
      linkobject=(UDPLink)links.get(_linkId);
    }
    return linkobject;

  }

  public void handleInboundUDPLinkRequest(InetAddress remoteAddress,
					  int remotePort) {
    String _linkId = "udp://" +
      remoteAddress.getHostAddress() + ":" + remotePort;
    if (UDPLinkFactory_Debug) {
      System.out.println(moduleName + "InboundConnection request received " +
			 "from [" + _linkId +"]");
    }

    if (links.containsKey(_linkId)) {
      System.out.println(moduleName + "InboundConnection => " +
			 "Link aleady exists to " + _linkId);
      return;
    }

    try {
      UDPLink _udpLink =
	new UDPLink(udpListenerSocket, remoteAddress, remotePort, this);
      _udpLink.setLinkId(_linkId);
      links.put(_linkId, _udpLink);
      transportHandler.registerLink(this, _udpLink);
    } catch (TransportException transEx) {
      System.out.println("moduleName" + transEx);
    } /*end try-catch */
  }


  public void reportLinkLoss(Link linkLost) {
    String _linkId = linkLost.getLinkId();
    if (_linkId == null) {
      System.out.println(moduleName + "ReportLinkLoss Link Id is null");
    }
    if (links.containsKey(_linkId)) {
      links.remove(_linkId);
      transportHandler.manageLinkLoss(linkLost);
      if (UDPLinkFactory_Debug) {
	System.out.println(moduleName + "Managed Link Loss");
      }
      return;
    }

    System.out.println(moduleName +"Unaware of link [" + _linkId +
		       "] whose connection loss is being reported");
  }


  public TransportHandler getTransportHandler() {
    if (transportHandler == null)
      System.out.println(moduleName + "TransportHandler reference is NULL");
    return transportHandler;
  }

  public static void main(String[] args) {
    if (args.length !=1) {
      System.out.println("Usage: java cgl.narada.transport.udp." +
			 "UDPLinkFactory <udpListenerPort>");
    }
    UDPLinkFactory factory = new UDPLinkFactory();
    Properties props = new Properties();
    props.put("UDPListenerPort", args[0]);
    factory.setLinkFactoryProperties(props);
    BufferedReader r = new BufferedReader(new InputStreamReader(System.in));
    String cmd;
    StringTokenizer tok;
    Hashtable links = new Hashtable();
    try {
      factory.loadLinkFactoryServices();
      System.out.println("\nType h for usage options");
      while(true) {
	cmd = r.readLine();

	if (cmd==null) {
	  factory.dispose();
	  break;
	}

	if (cmd.startsWith("h")) {
	  System.out.println("cr <host> <port> \t \t \t Creates a link");
	  System.out.println("cl <link-id> \t \t \t Closes a link");
	  continue;
	}

	if (cmd.startsWith("cr")) {
	  tok = new StringTokenizer(cmd, " ");
	  if (tok.countTokens() != 3) {
	    System.out.println("cr <host> <port> \t \t \t Creates a link");
	    continue;
	  }
	  Properties _props = new Properties();
	  tok.nextToken();
	  _props.put("hostname", tok.nextToken());
	  _props.put("portnum", tok.nextToken());
	  Link _link = factory.createLink(_props);
	  links.put(_link.getLinkId(), _link);
	  System.out.println("Created link with id " +
			     _link.getLinkId());
	} /** if (creation of link */


	if (cmd.startsWith("cl")) {
	  tok = new StringTokenizer(cmd, " ");
	  if (tok.countTokens() != 2) {
	    System.out.println("cl <link-id> \t \t \t Closes a link");
	    continue;
	  }
	  tok.nextToken();
	  String _linkId = tok.nextToken();
	  if (links.containsKey(_linkId)) {
	    Link _link = (Link) links.remove(_linkId);
	    _link.closeLink();
	  } else {
	    System.out.println("Specified link Id is not correct!");
	  }
	} /* if (closing of a specified link ) */



	if (cmd.equalsIgnoreCase("q") ) {
	  factory.dispose();
	  break;
	}
      }/*end while(true)*/
    } catch (IOException ioe) {
      ioe.printStackTrace();
    } catch (TransportException transEx) {
      System.out.println(transEx);
    }/*end try-catch */
    System.out.println("UDPLinkFactory: Exiting program ... end main()");
  }/*end-main*/

}

